// Karel Command Execution Logic - used for compilation

// import the Robot Karel class
import Robot = require("./RobotKarel");

// import instructions module
import Instr = require("./Instructions");

// import execution engine
import Engine = require("./ExecutionEngine");

// import dictionary
import Dic = require("./Dictionary");

// import localization
import Loc = require("../../UI/Localization");


// general base command abstract class
export class Command {

    // default ident increment for one nested block
    public mIdentIncr: number;

    // localization module
    private mLocalization: Loc.Localization;

    // constructor
    public constructor() {
        // default value for ident incrementation
        this.mIdentIncr = 2;
        this.mLocalization = Loc.Localization.Instance;
    }

    // command compilation  method
    public Compile(engine: Engine.ExecutionEngine, instrSet: Instr.InstructionSet): void {
        throw "Abstract method.";
    }

    // adds tab ident to the string
    public AddIdent(source: string, ident: number): string {
        var identStr = "";
        for (var i = 0; i < ident; i++)
            identStr += " ";
        return source + identStr;
    }

    // adds a new line to the source
    public AddNewLine(source: string): string {
        return source + "\r\n";
    }

    // adds end-of-block command
    public AddEndOfBlockCommand(source: string, ident: number): string {
        return this.CreateCommandLine(source, this.GetCmd("cmdEnd"), ident);
    }

    // creates the command line
    public CreateCommandLine(source: string, commandLine: string, ident: number) {
        source = this.AddIdent(source, ident);
        source += commandLine;
        return this.AddNewLine(source);
    }

    // abstract method for formatting the command
    public FormatCommand(source: string, ident: number): string {
        throw "Abstract method.";
    }

    // starts a DIV block
    public StartDIVBlock(source: string, cssClass: string): string {
        var div = "";
        if (cssClass == null) {
            div = "<div>";
        }
        else {
            div = "<div class=\"" + cssClass + "\">";
        } 

        return source+div;
    }

    // terminates DIV block
    public EndDIVBlock(source: string): string {
        return source + "</div>";
    }

    // creates a complete DIV block, usually one command
    public CreateDIVBlock(source: string, text: string, cssClass: string): string {
        var div = this.StartDIVBlock(source, cssClass);
        div += text;
        div = this.EndDIVBlock(div);
        return div;
    }

    // abstract method for generating card diagrams
    public GenerateDiagram(source: string, commandName: string): string {
        throw "Abstract method.";
    }

    // returns the localized command
    public GetCmd(key: string): string {
        return this.mLocalization.Get(key).toLocaleUpperCase();
    }
}

// implements the elementary command
export class InternalCommand extends Command {
    
    // the type of internal command
    private mCommandType: Instr.eInternalCommand;

    // constructor
    public constructor(type: Instr.eInternalCommand) {
        super();
        this.mCommandType = type;
    }

    // command compilation  method
    public Compile(engine: Engine.ExecutionEngine, instrSet: Instr.InstructionSet): void {
        var instruction = new Instr.InternalCommandInstruction(this.mCommandType);
        instrSet.AddInstruction(instruction);
    }

    // returns the command text
    private GetCommandText(): string {
        switch (this.mCommandType) {
            case Instr.eInternalCommand.Move:
                return this.GetCmd("cmdStep");
                break;
            case Instr.eInternalCommand.TurnLeft:
                return this.GetCmd("cmdTurnLeft");
                break;
            case Instr.eInternalCommand.Pick:
                return this.GetCmd("cmdPick");
                break;
            case Instr.eInternalCommand.Put:
                return this.GetCmd("cmdPut");
                break;
        }

        return "Uknown";
    }

    // method for formatting the command
    public FormatCommand(source: string, ident: number): string {
        return this.CreateCommandLine(source, this.GetCommandText(), ident);
    }

    // method for generating card diagrams
    public GenerateDiagram(source: string, commandName: string): string {
        return this.CreateDIVBlock(source, this.GetCommandText(), "command");
    }

}

// the sequence of commands class
export class CommandBlock extends Command {

    // the sequence of commands
    private mCommands: Array<Command>;

    // the current command
    private mCurrentCommandIdx: number;

    // constructor
    public constructor() {
        super();
        this.mCommands = new Array<Command>();
    }

    // property getter returning the child-command list
    public get ChildCommands(): Array<Command> {
        return this.mCommands;
    }

    // adds a command
    public AddCommand(command: Command) {
        this.mCommands.push(command);
    }

    // command compilation  method
    public Compile(engine: Engine.ExecutionEngine, instrSet: Instr.InstructionSet): void {
        this.CompileBlock(engine, instrSet);
    }

    // compiles the command block
    public CompileBlock(engine: Engine.ExecutionEngine, instrSet: Instr.InstructionSet): void {
        // iterate all commands and compile them
        for (var i = 0; i < this.mCommands.length; i++) {
            this.mCommands[i].Compile(engine, instrSet);
        }
    }

    // method for formatting the command
    public FormatCommand(source: string, ident: number): string {
        return this.FormatCommandBlock(source, ident);
    }

    // internal method for formating the command block
    public FormatCommandBlock(source: string, ident: number): string {
        // iterate all child commands
        for (var i = 0; i < this.mCommands.length; i++) {
            source = this.mCommands[i].FormatCommand(source, ident + this.mIdentIncr);
        }

        // add end-of-block command
        return this.AddEndOfBlockCommand(source, ident);
    }

    // method for generating card diagrams
    public GenerateBlockDiagram(source: string, commandName: string): string {
        // add command block header
        source = this.StartDIVBlock(source, "commandBlock");

        // iterate all child commands
        for (var i = 0; i < this.mCommands.length; i++) {
            source = this.mCommands[i].GenerateDiagram(source, commandName);
        }

        // terminate command block
        source = this.EndDIVBlock(source);

        // add command END
        source = this.CreateDIVBlock(source, this.GetCmd("cmdEnd"), null);

        return source;
    }
}


// defines the user command class
export class UserCommand extends CommandBlock {

    // name of the command
    private mName: string;

    // the command source code
    private mSourceCode: string;

    // constructor
    public constructor(name: string) {
        super();
        this.mName = name;
    }

    // returns the name of the command
    public GetName(): string {
        return this.mName;
    }

    // set the source code
    public SetSourceCode(sourceCode: string): void {
        this.mSourceCode = sourceCode;
    }

    // returns the source code of the command
    public GetSourceCode(): string {
        return this.mSourceCode;
    }

    // command compilation  method
    public Compile(engine: Engine.ExecutionEngine, instrSet: Instr.InstructionSet): void {
        // create and add CALL instruction
        var instruction = new Instr.CallInstruction(this.mName);
        instrSet.AddInstruction(instruction);

        // check sub-command is compiled
        if (engine.IsCommandCompiled(this.mName) == false) {

            // get the sub-command
            var cmd = Dic.Dictionary.Instance.GetUserCommandByName(this.mName);
            if (cmd == null) {
                throw "Unknown command.";
            }

            // compile the sub-command
            cmd.CompileCommand(engine);

        }
    }

    // the main API method for compilation of command before running it
    public CompileCommand(engine: Engine.ExecutionEngine): Instr.InstructionSet {
        // init compilation
        var instrSet = engine.StartCompilation(this.mName);

        // compile the block
        this.CompileBlock(engine, instrSet);
        
        // finalize compilation
        engine.FinalizeCompilation(instrSet);

        // return the instruction set
        return instrSet;
    }

    // adds the call command
    public FormatCommand(source: string, ident: number): string {
        return this.CreateCommandLine(source, this.mName.toLocaleUpperCase(), ident);
    }

    // the main API method for formatting the user command
    public FormatSourceCode(): string {
        // add the command name
        var source = this.mName.toLocaleUpperCase();
        source = this.AddNewLine(source);

        // add the internal block
        source = this.FormatCommandBlock(source, 0);

        // return the source code
        return source.toLocaleUpperCase();
    }

    // method for generating card diagrams
    public GenerateDiagram(source: string, commandName: string): string {
        var cssClass = this.mName.toLocaleUpperCase() == commandName.toLocaleUpperCase() ? "commandRecursive" : "callCommand";
        return this.CreateDIVBlock(source, this.mName.toLocaleUpperCase(), cssClass);
    }

    // the main API method for generating the card diagram of command
    public GenerateCardDiagram(): string {
        var source = "";

        // start DIV block and generate header
        source = this.StartDIVBlock(source, "userCommand");
        source = this.CreateDIVBlock(source, this.mName.toLocaleUpperCase(), null);

        // generate the command block
        source = this.GenerateBlockDiagram(source, this.mName);

        // terminate DIV block
        source = this.EndDIVBlock(source);

        return source;
    }
}


// the Repeat command
export class RepeatCommand extends CommandBlock {

    // the repetition count
    private mRepetitionCount: number;

    // constructor
    public constructor() {
        super();
        this.mRepetitionCount = 0;
    }

    // sets the number of repetitions
    public SetRepetitionCount(repCount: number) {
        this.mRepetitionCount = repCount;
    }

    // command compilation  method
    public Compile(engine: Engine.ExecutionEngine, instrSet: Instr.InstructionSet): void {
        // save current pointer
        var startIndex = instrSet.InstructionCount;

        // request register
        var registerIndex = engine.RequestRegister();

        // create and add repeat instruction
        var repeatInstr = new Instr.RepeatInstruction(this.mRepetitionCount, registerIndex);
        instrSet.AddInstruction(repeatInstr);

        // compile block
        this.CompileBlock(engine, instrSet);

        // save next pointer
        var endIndex = instrSet.InstructionCount;

        // add end of block instruction with calc offset
        var offset = startIndex - endIndex;
        instrSet.AddInstruction(new Instr.EndOfBlockInstruction(offset));

        // set the pass-by block instruction offset
        repeatInstr.SetEndOfBlockIndex(instrSet.InstructionCount - startIndex);
    }

    // returns the command text
    private GetCommandText(): string {
        return this.GetCmd("cmdRepeat") + " " + this.mRepetitionCount.toString() + "x";
    }

    // adds the repeat command
    public FormatCommand(source: string, ident: number): string {
        // add repeat header
        source = this.CreateCommandLine(source, this.GetCommandText(), ident);

        // add the command block
        source = this.FormatCommandBlock(source, ident);

        // return result
        return source;
    }

    // method for generating card diagrams
    public GenerateDiagram(source: string, commandName: string): string {
        // start DIV block and generate header
        source = this.StartDIVBlock(source, "cycleCommand");
        source = this.CreateDIVBlock(source, this.GetCommandText(), null);

        // generate the command block
        source = this.GenerateBlockDiagram(source, commandName);

        // terminate DIV block
        source = this.EndDIVBlock(source);

        return source;
    }
}

// the While command
export class WhileCommand extends CommandBlock {

    // condition attribute
    private mCondition: Instr.Condition;

    // constructor
    public constructor() {
        super();
        this.mCondition = null;
    }

    // sets the condition
    public SetCondition(condition: Instr.Condition): void {
        this.mCondition = condition;
    }

    // command compilation  method
    public Compile(engine: Engine.ExecutionEngine, instrSet: Instr.InstructionSet): void {
        // save current pointer
        var startIndex = instrSet.InstructionCount;

        // create and add While instruction
        var whileInstr = new Instr.WhileInstruction(this.mCondition);
        instrSet.AddInstruction(whileInstr);

        // compile block
        this.CompileBlock(engine, instrSet);

        // save next pointer
        var endIndex = instrSet.InstructionCount;

        // add end of block instruction with calc offset
        var offset = startIndex - endIndex;
        instrSet.AddInstruction(new Instr.EndOfBlockInstruction(offset));

        // set the pass-by block instruction offset
        whileInstr.SetEndOfBlockOffset(instrSet.InstructionCount - startIndex);
    }

    // returns the command text
    private CreateCommandText(): string {
        return this.GetCmd("cmdWhile") + " " + this.mCondition.ToString();
    }

    // adds the while command
    public FormatCommand(source: string, ident: number): string {
        // add while header
        source = this.CreateCommandLine(source, this.CreateCommandText(), ident);

        // add the command block
        source = this.FormatCommandBlock(source, ident);

        // return result
        return source;
    }

    // method for generating card diagrams
    public GenerateDiagram(source: string, commandName: string): string {
        // start DIV block and generate header
        source = this.StartDIVBlock(source, "cycleCommand");
        source = this.CreateDIVBlock(source, this.CreateCommandText(), null);

        // generate the command block
        source = this.GenerateBlockDiagram(source, commandName);

        // terminate DIV block
        source = this.EndDIVBlock(source);

        return source;
    }

}

// the IF command
export class IfCommand extends Command {

    // condition attribute
    private mCondition: Instr.Condition;
    private mThenBlock: CommandBlock;
    private mElseBlock: CommandBlock;

    // constructor
    public constructor() {
        super();
        this.mCondition = null;
        this.mThenBlock = null;
        this.mElseBlock = null;
    }

    // sets the condition
    public SetCondition(condition: Instr.Condition): void {
        this.mCondition = condition;
    }


    // adds the THEN block
    public AttachThenBlock(thenBlock: CommandBlock): void {
        this.mThenBlock = thenBlock;
    }

    // adds the ELSE block
    public AttachElseBlock(elseBlock: CommandBlock): void {
        this.mElseBlock = elseBlock;
    }

    // property getter for THEN block
    public get ThenBlock(): CommandBlock {
        return this.mThenBlock;
    }

    // property getter for ELSE block
    public get ElseBlock(): CommandBlock {
        return this.mElseBlock;
    }

    // command compilation  method
    public Compile(engine: Engine.ExecutionEngine, instrSet: Instr.InstructionSet): void {
        // create IF instruction
        var startIndex = instrSet.InstructionCount;
        var ifInstruction = new Instr.IfInstruction(this.mCondition);
        instrSet.AddInstruction(ifInstruction);

        // compile THEN block
        this.mThenBlock.Compile(engine, instrSet);

        // save current pointer
        var instrIndex = instrSet.InstructionCount;

        // if ELSE block defined
        if (this.mElseBlock != null) {
            var endOfBlock = new Instr.EndOfBlockInstruction(0);
            instrSet.AddInstruction(endOfBlock);

            ifInstruction.SetElseBlockOffset(instrSet.InstructionCount - startIndex);

            // compile ELSE block
            this.mElseBlock.Compile(engine, instrSet);

            // calculate offset
            var offset = instrSet.InstructionCount - instrIndex;
            endOfBlock.SetOffset(offset);
        }
        else {
            ifInstruction.SetEndBlockOffset(instrSet.InstructionCount - startIndex);
        }
    }

    // returns the command text
    private CreateCommandText(): string {
        return this.GetCmd("cmdIf") + " " + this.mCondition.ToString();
    }

    // adds the while command
    public FormatCommand(source: string, ident: number): string {
        // add IF header
        source = this.CreateCommandLine(source, this.CreateCommandText(), ident);

        // add the command block
        source = this.mThenBlock.FormatCommandBlock(source, ident);

        // check else block
        if (this.mElseBlock != null) {
            // add ELSE header
            source = this.CreateCommandLine(source, this.GetCmd("cmdElse"), ident);

            // format else block
            source = this.mElseBlock.FormatCommandBlock(source, ident);
        }

        // return result
        return source;
    }

    // method for generating card diagrams
    public GenerateDiagram(source: string, commandName: string): string {
        // start DIV block and generate header
        var cssClass = null;
        if (this.mElseBlock != null)
            cssClass = "ifWrapper";
        source = this.StartDIVBlock(source, cssClass);
        source = this.StartDIVBlock(source, "branchCommand");
        source = this.CreateDIVBlock(source, this.CreateCommandText(), null);

        // generate the command block
        source = this.mThenBlock.GenerateBlockDiagram(source, commandName);

        // terminate DIV block
        source = this.EndDIVBlock(source);

        // check ELSE block
        if (this.mElseBlock != null) {
            source = this.StartDIVBlock(source, "branchCommand");
            source = this.CreateDIVBlock(source, this.GetCmd("cmdElse"), null)

            // generate the command block
            source = this.mElseBlock.GenerateBlockDiagram(source, commandName);

            // terminate DIV block
            source = this.EndDIVBlock(source);
        }

        source = this.EndDIVBlock(source);

        return source;
    }
}
